/*
 * Copyright (c) 2020 Ubique Innovation AG <https://www.ubique.ch>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * SPDX-License-Identifier: MPL-2.0
 */

package org.dpppt.backend.sdk.ws.config;

import org.apache.commons.io.IOUtils;
import org.dpppt.backend.sdk.data.RedeemDataService;
import org.dpppt.backend.sdk.ws.radarcovid.client.ValidationClientService;
import org.dpppt.backend.sdk.ws.security.*;
import org.dpppt.backend.sdk.ws.security.KeyVault.PublicKeyNoSuitableEncodingFoundException;
import org.dpppt.backend.sdk.ws.util.KeyHelper;
import org.dpppt.backend.sdk.ws.util.ValidationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.time.Duration;
import java.util.Base64;

@Configuration
@EnableWebSecurity
@Profile(value = "jwt")
public class MultipleJWTConfig {

  public static class CommonJWTBase extends WebSecurityConfigurerAdapter {
    @Value("${ws.app.jwt.publickey}")
    String publicKey;

    @Value("${ws.app.jwt.algorithm:RSA}")
    String algorithm;

    @Value("${ws.app.jwt.maxValidityMinutes: 60}")
    int maxValidityMinutes;

    @Value("${ws.retentiondays: 14}")
    int retentionDays;

    @Autowired @Lazy RedeemDataService redeemDataService;

    protected String loadPublicKey() throws IOException {
      if (publicKey.startsWith("keycloak:")) {
        String url = publicKey.replace("keycloak:/", "");
        return KeyHelper.getPublicKeyFromKeycloak(url);
      }
      InputStream in = null;
      if (publicKey.startsWith("classpath:/")) {
        in = new ClassPathResource(publicKey.substring(11)).getInputStream();
        return readAsStringFromInputStreamAndClose(in);
      } else if (publicKey.startsWith("file:/")) {
        in = new FileInputStream(publicKey);
        return readAsStringFromInputStreamAndClose(in);
      }
      //return publicKey;
      return new String(Base64.getDecoder().decode(publicKey));
    }

    private String readAsStringFromInputStreamAndClose(InputStream in) throws IOException {
      String result = IOUtils.toString(in, "UTF-8");
      in.close();
      return result;
    }
  }

  @Order(1)
  public static class WSJWTSecondConfig extends CommonJWTBase {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      // @formatter:off
      http.sessionManagement()
          .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
          .and()
          .antMatcher("/v1/gaen/exposednextday")
          .cors()
          .and()
          .authorizeRequests()
          .antMatchers(HttpMethod.POST, "/v1/gaen/exposednextday")
          .authenticated()
          .anyRequest()
          .permitAll()
          .and()
          .oauth2ResourceServer()
          .jwt()
          .decoder(jwtDecoderSecondDay());
      // @formatter:on
    }

    @Autowired @Lazy KeyVault keyVault;

    @Bean
    public JWTValidator jwtValidatorGAEN() {
      return new JWTValidator(redeemDataService, Duration.ofDays(3));
    }

    @Bean
    public JWTClaimSetConverter claimConverterGAEN() {
      return new JWTClaimSetConverter();
    }

    @Bean
    public JwtDecoder jwtDecoderSecondDay()
        throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {

      var jwtDecoder = new GaenJwtDecoder(keyVault.get("nextDayJWT").getPublic());
      // jwtDecoder.setClaimSetConverter(claimConverterGAEN());

      OAuth2TokenValidator<Jwt> defaultValidators = JwtValidators.createDefault();
      jwtDecoder.setJwtValidator(
          new DelegatingOAuth2TokenValidator<>(defaultValidators, jwtValidatorGAEN()));
      return jwtDecoder;
    }
  }

  @Order(2)
  public static class WSJWTConfig extends CommonJWTBase {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      // @formatter:off
      http.sessionManagement()
          .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
          .and()
          // .regexMatcher("/v1/(exposed|exposedlist|gaen/exposed)")
          .csrf()
          .disable()
          .cors()
          .and()
          .authorizeRequests()
          .antMatchers(HttpMethod.POST, "/v1/exposed", "/v1/exposedlist", "/v1/gaen/exposed")
          .authenticated()
          .anyRequest()
          .permitAll()
          .and()
          .oauth2ResourceServer()
          .jwt();
      // @formatter:on
    }

    @Bean
    public JWTValidator jwtValidator() {
      return new JWTValidator(redeemDataService, Duration.ofMinutes(maxValidityMinutes));
    }

    @Bean
    public ValidateRequest gaenRequestValidator(ValidationUtils gaenValidationUtils,
                                                ValidationClientService validationClientService) {
      return new org.dpppt.backend.sdk.ws.security.gaen.JWTValidateRequest(gaenValidationUtils,
                                                                           validationClientService);
    }

    @Bean
    public JWTClaimSetConverter claimConverter() {
      return new JWTClaimSetConverter();
    }

    @Bean
    @Primary
    public JwtDecoder jwtDecoder()
        throws InvalidKeySpecException, NoSuchAlgorithmException, IOException,
            PublicKeyNoSuitableEncodingFoundException {
      GaenJwtDecoder jwtDecoder =
          new GaenJwtDecoder(KeyVault.loadPublicKey(loadPublicKey(), algorithm));

      OAuth2TokenValidator<Jwt> defaultValidators = JwtValidators.createDefault();
      jwtDecoder.setJwtValidator(
          new DelegatingOAuth2TokenValidator<>(defaultValidators, jwtValidator()));
      return jwtDecoder;
    }
  }
}
